package nasp.labos.drugi.GA;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class GA {

	private int popSize;
	private float pMutation;
	private int maxIteration;
	private List<String[]> individuals;
	private List<Double> values;
	private int dimension;
	private int precision;
	private double upperLimit;
	private double lowerLimit;
	private int numBits;
	private int f;
	
	
	public GA(int popSize, float pMutation, int maxIteration, int f, int dimension) {
		this.popSize = popSize;
		this.pMutation = pMutation;
		this.maxIteration = maxIteration;
		this.individuals =  new ArrayList<String[]>(popSize);
		this.values = new ArrayList<Double>(popSize);
		this.f = f;
		if(f==4)
			this.dimension = 2;
		else if(f==3)
			this.dimension = 5;
		else 
			this.dimension = dimension;
		precision = 3;
		upperLimit = 100;
		lowerLimit = -100;
		numBits = 20;
	}

	
	public GA(int popSize, float pMutation, int maxIteration, int dimension,
			int precision, double upperLimit, double lowerLimit, int numBits,
			int f) {
		this.popSize = popSize;
		this.pMutation = pMutation;
		this.maxIteration = maxIteration;
		this.individuals =  new ArrayList<String[]>(popSize);
		this.values = new ArrayList<Double>(popSize);
		if(f==4)
			this.dimension = 2;
		else if(f==3)
			this.dimension = 5;
		else 
			this.dimension = dimension;
		this.precision = precision;
		this.upperLimit = upperLimit;
		this.lowerLimit = lowerLimit;
		this.numBits = numBits;
		this.f = f;
	}


	public void runGA() {
		createPop();
		evalPop();
		
		int[] rand = new int[3];
		String[] child = new String[dimension];
		int i = 0;
		boolean contains;
		do{
			rand = get3Random();
			worstOf3(rand);
			contains = false;
			while(!contains){
				child = cross(rand[1],rand[2]);
				mutate(child);
				if(!compare(child))
					contains = true;
			}
			individuals.set(rand[0], child);
			values.set(rand[0], evalF(child));
			if(i%100 == 0 && i<= 1000)System.out.print(printMin(i));
			if(i%1000 == 0 && i>1000)System.out.print(printMin(i));
		}while(++i<maxIteration);
		System.out.println("rez:");
		System.out.print(printMin(i));
	}

	public String printMin(int i) {
		int min = findMinimum();
		StringBuffer sb = new StringBuffer();
		sb.append(i+", "+min+": ");
		sb.append(printDec(min));
		sb.append("\n");
		//printValue(min);
		return sb.toString();
	}

	public void createPop(){
		for(int i = 0; i< popSize; i++)
			individuals.add(createIndividual());
	}

	private String[] createIndividual() {
		StringBuffer sb;
		String[] number = new String[dimension];
		Random random = new Random();
		for(int n = 0;n<dimension;n++){
			sb=new StringBuffer();
			for(int i = 0; i<numBits; i++)
				sb.append(random.nextInt(2));
			number[n] = sb.toString();
		}
		return number;
	}
	
	public void evalPop(){
		for(int i = 0; i<popSize;i++)
			values.add(evalF(individuals.get(i)));
	}
	
	public double evalF(String[] x){
		switch(f){
			case 3: return f3(evalX(x));
			case 4: return f4(evalX(x[0]), evalX(x[1]));
			case 6: return f6(evalX(x));
			case 7: return f7(evalX(x));
			default: return 0;
		}
	}
	
	private double evalX(String number){
		double x = Integer.parseInt(number, 2);
		return (lowerLimit + ( x * (upperLimit-lowerLimit))/(Math.pow(2, numBits))-1);
	}
	
	private double[] evalX(String[] number){
		double[] eval = new double[dimension];
		for(int i = 0;i<dimension;i++)
			eval[i] = evalX(number[i]);
		return eval;
	}
	
	public int[] get3Random(){
		int[] rand = new int[3];
		Random random = new Random();
		rand[0] = random.nextInt(popSize);
		do{
			rand[1] = random.nextInt(popSize);
		}while(rand[0] == rand[1]);
		do{
			rand[2] = random.nextInt(popSize);
		}while(rand[2] == rand[0] || rand[2] == rand[1]);
		return rand;
	}

	public void worstOf3(int[] rand) {
		int t;
		if(values.get(rand[1]) > values.get(rand[0])){
			t = rand[0];
			rand[0] = rand[1];
			rand[1] = t;
		}
		if(values.get(rand[2]) > values.get(rand[1])){
			t = rand[1];
			rand[1] = rand[2];
			rand[2] = t;
		}
		if(values.get(rand[1]) > values.get(rand[0])){
			t = rand[0];
			rand[0] = rand[1];
			rand[1] = t;
		}
	}
	
	public String[] cross(int f, int s) {
		StringBuffer first;
		StringBuffer second;
		int pointOfCross;
		String[] child = new String[dimension];
		for(int i=0;i<dimension;i++){
			Random random = new Random();
			first = new StringBuffer(individuals.get(f)[i]);
			second = new StringBuffer(individuals.get(s)[i]);
			pointOfCross = random.nextInt(numBits);
			if(random.nextDouble()<0.5)
				child[i] = second.replace(pointOfCross+1, numBits, first.substring(pointOfCross+1)).toString();
			else
				child[i] = first.replace(0, pointOfCross, second.substring(0,pointOfCross)).toString();
		}
		return child;
	}

	public void mutate(String[] child) {
		Random rand = new Random();
		StringBuffer number;
		for(int n = 0; n<dimension;n++){
			number = new StringBuffer(child[n]);
			for(int i = 0;i<numBits;i++)
				if(rand.nextFloat() < pMutation){
					if(rand.nextFloat() < 0.5)
						number.setCharAt(i, '0');
					else 
						number.setCharAt(i, '1');
					child[n] = number.toString();
				}
			}
	}
	
	public boolean compare(String[] child) {
		boolean same = false;
		for(String[] indi : individuals){
			same = false;
			for(int i = 0;i<dimension;i++)
				if(child[i].compareTo(indi[i]) == 0)
					same = true;
			if(same == true)
				return same;
		}
		return same;
	}

	public int findMinimum() {
		double min = Double.MAX_VALUE;
		int index = -1;
		for(int i = 0; i<popSize; i++){
			if(values.get(i) < min){
				min = values.get(i);
				index = i;
			}
		}
		return index;
	}

	private void printDec(){
		for(int i = 0; i<popSize; i++){
			printDec(i);
		}
	}

	private void printValues(){
		int i = 0;
		for(Double value : values)
			System.out.println(i+++": " + value);
	}
	
	private void printValue(int index){
		System.out.println("f = "+values.get(index));
	}
	public String printDec(int index){
		StringBuffer sb = new StringBuffer();
		for(int n = 0; n<dimension;n++){
			sb.append(getDec(individuals.get(index)[n]));
			sb.append(" ");
		}
		sb.deleteCharAt(sb.length()-2);
		sb.deleteCharAt(sb.length()-1);
		//System.out.println(sb.toString());
		return sb.toString();
	}
	
	private String getDec(String number){
		StringBuffer format = new StringBuffer();
		format.append("00.");
		for(int i = 0; i < precision; i++)
			format.append("0");
		DecimalFormat df = new DecimalFormat(" "+format+";-"+format);
		return df.format(evalX(number));
	}
	
	public List<Double> getValues() {
		return values;
	}
	
	public List<String[]> getIndividuals() {
		return individuals;
	}
	
	public int getDimension() {
		return dimension;
	}
	
	public int getMaxIteration() {
		return maxIteration;
	}
	
	public static void main(String[] args) {
		GA ga = new GA(30, 0.02f, 10000, 3, 2);
		ga.runGA();
	}
	
	private double f3(double[] x){
		double[] p = {20,30,0,80,-10};
		double rez = 0;
		for(int i=0;i<5;i++)
			rez += Math.pow(x[i]-p[i], 2);
		return rez;
	}
	
	private double f4(double x, double y){
		return Math.abs((x-y)*(x+y)) + Math.pow(Math.pow(x, 2)+Math.pow(y, 2), 0.5);
	}
	
	private double f6(double[] x){
		double sumx2 = 0;
		for(int i = 0; i<dimension;i++)
			sumx2 += x[i] * x[i];
		return (0.5 - (Math.pow(Math.sin(Math.sqrt(sumx2)), 2) -0.5) / Math.pow(1.0+0.001*sumx2, 2));
	}
	
	private double f7(double[] x){
		double sumx2 = 0;
		for(int i = 0; i<dimension;i++)
			sumx2 += x[i] * x[i];
		return Math.pow(sumx2, 0.25)*(Math.pow(Math.sin(50*Math.pow(sumx2, 0.1)), 2) +1.0);
	}
}
